#include <buola/gui.h>

#include <buola/algorithm/container.h>

#include <buola/image/ctextlayout.h>
#include <buola/image/cfont.h>
#include <buola/gui/cicon.h>
#include <buola/gui/cconfig.h>
#include <buola/gui/cpropertyfield.h>
#include <buola/image/cgc.h>
#include <buola/utility/cregion.h>

#include <buola/widgets/ctreebox.h>
#include <buola/widgets/crulerbox.h>
#include <buola/widgets/ceditbox.h>
#include <buola/widgets/cscrollbar.h>

#include "ctbtreerenderer.h"

namespace buola { namespace gui {

class CTBTreeRendererData
{
public:
    //preshape
    double mHeight;             //minimum height for the item

    //shape
    CPoint_d mExpandPos;
    CPoint_d mIconPos;            //position inside the whole item
    CPoint_d mTextPos;            //inside the item
    img::CTextLayout mTextLayout;

    //pos
    CPoint_d mPos;                //absolute position of the item
    int mPosIndex;

    std::vector<std::unique_ptr<img::CTextLayout>> mFieldLayouts;
};

CTBTreeRenderer::CTBTreeRenderer(CTreeBox *pTreeBox)
            :CTBRenderer(pTreeBox)
{
    if(mTreeBox->GetStyle(CWidget::STYLE_SMALLFONT))
    {
        mFont=img::CFont::GetStock(L"small");
        mBoldFont=img::CFont::GetStock(L"smallbold");
    }
    else
    {
        mFont=img::CFont::GetStock(L"control");
        mBoldFont=img::CFont::GetStock(L"controlbold");
    }

    for(int i=0;i<mTreeBox->GetNodeCount();i++)
    {
        InitNode(mTreeBox->GetNodes()[i]);
    }

    mFirstField=new CTreeBox::SField;
    mFirstField->mX=0;
    mFirstField->mW=mTreeBox->mTWF.size()?mTreeBox->mTWF[0]->mX:
                        mTreeBox->Size().x;
    mFirstField->mIndex=-1;
    mFirstField->mField=new CPropertyField(L"string",L"name",L"name");

    if(mTreeBox->GetStyle(CTreeBox::STYLE_NORULER))
    {
        mRuler=nullptr;
    }
    else
    {
        mRuler=new CRulerBox;
        mTreeBox->Place(mRuler);
///\todo         attach_ruler_bars(mRuler,nullptr);
        FillRuler();
        mRuler->eMoved.Connect(&CTBTreeRenderer::OnRulerMoved,this,_1,_2);

        CalcAll();
    }
}

CTBTreeRenderer::~CTBTreeRenderer()
{
    for(int i=0;i<mTreeBox->GetNodeCount();i++)
    {
        DeinitNode(mTreeBox->GetNodes()[i]);
    }

    delete mFirstField->mField;
    delete mFirstField;
}

///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////
///////////////////// Internal functions //////////////////////
///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////

void CTBTreeRenderer::CalcAll()
{
    if(mTreeBox->GetStyle(CTreeBox::STYLE_NOICON))
        mIconSize={0,0};
    else
        mIconSize=CSize_d(mTreeBox->GetIconSize());

    mTextEnd=mTreeBox->Size().x;
    mTotalEnd=mTextEnd;

    for(int i=0;i<mTreeBox->GetFieldCount();i++)
    {
        CTreeBox::SField *lF=mTreeBox->GetFields()[i];

        if(lF->mField->mStyle&CPropertyField::PLF_HIDDEN) continue;

        mTextEnd=lF->mX-4;
        break;
    }

    for(int i=mTreeBox->GetFieldCount()-1;i>=0;i--)
    {
        CTreeBox::SField *lF=mTreeBox->GetFields()[i];

        if(lF->mField->mStyle&CPropertyField::PLF_HIDDEN) continue;

        mTotalEnd=lF->mX+lF->mW;
        break;
    }

    mMinTextHeight=mFont->GetHeight();
    PreshapeAll();
    ShapeAll();
    PosAll();
}

void CTBTreeRenderer::InitNode(CTreeBox::SNode *pI)
{
    CTBTreeRendererData *lD=new CTBTreeRendererData;

    pI->mRendererData=lD;
}

void CTBTreeRenderer::DeinitNode(CTreeBox::SNode *pI)
{
    delete (CTBTreeRendererData*)pI->mRendererData;
    pI->mRendererData=nullptr;
}

void CTBTreeRenderer::PreshapeNode(CTreeBox::SNode *pI)
{
    CTBTreeRendererData *lD=(CTBTreeRendererData*)pI->mRendererData;

    lD->mHeight=max(mIconSize.y,mMinTextHeight)+4;

    if(mIconSize.x>mMaxIconWidth)
    {
        mMaxIconWidth=mIconSize.x;
    }
    if(lD->mHeight>mMaxHeight)
    {
        mMaxHeight=lD->mHeight;
    }
}

void CTBTreeRenderer::ShapeNode(CTreeBox::SNode *pI)
{
    CTBTreeRendererData *lD=(CTBTreeRendererData*)pI->mRendererData;

    double lX=(pI->mDepth+1)*mIndent+4;

//Pos expand button
    lD->mExpandPos.x=lX-mIndent+4;
    lD->mExpandPos.y=lD->mHeight/2;

//Pos icon
    lD->mIconPos.x=lX;
    lD->mIconPos.y=lD->mHeight/2-mIconSize.y/2;

//Shape & pos text
    if(mMaxIconWidth)
        lD->mTextPos.x=lX+mMaxIconWidth+4;
    else
        lD->mTextPos.x=lX+2;
    img::PFont lFont=(pI==mTreeBox->GetCursorNode())?mBoldFont:mFont;
    lD->mTextLayout.SetDefaultFont(lFont);
    lD->mTextLayout.SetText(pI->mText);
    lD->mTextLayout.SetWidth(mTextEnd-lD->mTextPos.x);
    lD->mTextLayout.SetHeightLines(1);

    lD->mTextPos.y=lD->mHeight/2-lD->mTextLayout.GetSize().y/2;
    
//Shape fields
    construct_resize(lD->mFieldLayouts,mTreeBox->GetFieldCount());

    for(int i=mTreeBox->GetFieldCount()-1;i>=0;i--)
    {
        CTreeBox::SField *lF=mTreeBox->GetFields()[i];

        if(lF->mField->mStyle&CPropertyField::PLF_HIDDEN) continue;

        lD->mFieldLayouts[i]->SetDefaultFont(lFont);
        lD->mFieldLayouts[i]->SetText(pI->ValueAsText(lF->mIndex));
        lD->mFieldLayouts[i]->SetWidth(lF->mW-4);
        lD->mFieldLayouts[i]->SetHeightLines(1);
    }

}

void CTBTreeRenderer::DrawNode(img::CGC *pGC,CTreeBox::SNode *pI)
{
    CTBTreeRendererData *lD=(CTBTreeRendererData*)pI->mRendererData;

    CPoint_d lPos=lD->mPos-mTreeBox->GetScrollPos();
    CPoint_d lTextPos=lPos+lD->mTextPos;

    if(!(mTreeBox->mDropping.mInside&&pI==mTreeBox->mDropping.mLast))
    {
        if(pI->mSelected)
        {
            pGC->SetSource(gConfig.mColors.Get(L"selection_back"));
            pGC->Path().Rect(CPoint_d(0,lPos.y),CSize_d(mTotalEnd,lD->mHeight));
            pGC->FillPath();
        }
        else if(lD->mPosIndex%2)
        {
//            pGC->SetSource(RGB(237,243,254));
//            pGC->Path().Rect(CPoint_d(0,lPos.y),CSize_d(mTotalEnd,lD->mHeight));
//            pGC->FillPath();
        }
    }

    if(mTreeBox->GetStyle(CTreeBox::STYLE_HIERARCHYLINES))
    {
        pGC->SetLineWidth(1.0);
        pGC->SetSource(img::rgba8(0,0,0,40));
        CTreeBox::SNode *lAncestor=pI->mParent;
        for(int i=1;i<pI->mDepth&&lAncestor;i++)
        {
            if(lAncestor->mNext)
            {
                pGC->Path().MoveTo(CPoint_d(lD->mExpandPos.x+7-i*mIndent+0.5,lPos.y));
                pGC->Path().LineTo(CPoint_d(lD->mExpandPos.x+7-i*mIndent+0.5,lPos.y+lD->mHeight));
            }
            lAncestor=lAncestor->mParent;
        }

        if(pI->mExpanded&&pI->mChildren.size())
        {
            pGC->Path().MoveTo(CPoint_d(lD->mExpandPos.x+mIndent+7+0.5,lPos.y+lD->mHeight/2));
            pGC->Path().LineTo(CPoint_d(lD->mExpandPos.x+mIndent+7+0.5,lPos.y+lD->mHeight));
        }

        if(pI->mDepth)
        {
            if(pI->mNext)
            {
                pGC->Path().MoveTo(CPoint_d(lD->mExpandPos.x+7+0.5,lPos.y));
                pGC->Path().LineTo(CPoint_d(lD->mExpandPos.x+7+0.5,lPos.y+lD->mHeight));
            }
            else
            {
                pGC->Path().MoveTo(CPoint_d(lD->mExpandPos.x+7+0.5,lPos.y));
                pGC->Path().LineTo(CPoint_d(lD->mExpandPos.x+7+0.5,lPos.y+lD->mHeight/2));
            }

            pGC->Path().MoveTo(CPoint_d(lD->mExpandPos.x+7,lPos.y+lD->mHeight/2+0.5));
            pGC->Path().LineTo(CPoint_d(lD->mExpandPos.x+7+mIndent,lPos.y+lD->mHeight/2+0.5));
        }

        pGC->StrokePath();
    }

    if(pI->CanBeExpanded())
    {
        CPoint_d lP(lPos+lD->mExpandPos);
        if(pI->mExpanded)
        {
            pGC->Path().MoveTo(CPoint_d(lP.x-3.5,lP.y-2.5));
            pGC->Path().LineTo(CPoint_d(lP.x+4.5,lP.y-2.5));
            pGC->Path().LineTo(CPoint_d(lP.x+0.5,lP.y+4.0));
            pGC->Path().Close();
        }
        else
        {
            pGC->Path().MoveTo(CPoint_d(lP.x-2.5,lP.y-3.5));
            pGC->Path().LineTo(CPoint_d(lP.x-2.5,lP.y+4.5));
            pGC->Path().LineTo(CPoint_d(lP.x+4.0,lP.y+0.5));
            pGC->Path().Close();
        }

        pGC->SetLineWidth(1.0);
        pGC->SetSource(img::rgb8(115,115,115));
        pGC->FillPath(true);
        pGC->StrokePath();
    }

    if(pI->mIcon)
    {
        pI->mIcon->Draw(pGC,lPos+lD->mIconPos,mTreeBox->GetIconSize(),
                        pI->mSelected?CIcon::SELECTED:CIcon::NORMAL);
    }

    pGC->SetSource(gConfig.mColors.Get(pI->mSelected?L"selection_text":L"normal_text"));

    if(pI==mTreeBox->GetCursorNode())
        pGC->SetFont(mBoldFont);
    else
        pGC->SetFont(mFont);

    lD->mTextLayout.Draw(lTextPos,pGC);

    for(int i=0;i<mTreeBox->GetFieldCount();i++)
    {
        CTreeBox::SField *lF=mTreeBox->GetFields()[i];

        if(lF->mField->mStyle&CPropertyField::PLF_HIDDEN) continue;

        lD->mFieldLayouts[i]->Draw(CPoint_d(lF->mX,lTextPos.y),pGC);
    }

    if(mTreeBox->mDropping.mInside&&pI==mTreeBox->mDropping.mLast)
    {
        img::CColor lColor;
        if(mTreeBox->mDropping.mAccept)
            lColor=img::rgba8(100,200,100,80);
        else
            lColor=img::rgba8(200,100,100,80);
        lColor.Premultiply();
        pGC->SetSource(lColor);
        pGC->Path().Rect(CPoint_d(0,lPos.y),CSize_d(mTotalEnd,lD->mHeight));
        pGC->FillPath();
    }
}

void CTBTreeRenderer::RedrawNode(CTreeBox::SNode *pI,bool pRedraw)
{
    CTBTreeRendererData *lD=(CTBTreeRendererData*)pI->mRendererData;

    CRect_d lRect(lD->mPos-mTreeBox->GetScrollPos(),
                CSize_d(mTotalEnd,lD->mHeight));

    img::CGC *lGC=mTreeBox->StartPainting(CRegion(lRect),pRedraw);
    DrawNode(lGC,pI);

    if(mTreeBox->mSelecting&&mTreeBox->mLastDragPoint!=mTreeBox->mDragPoint)
    {
        CRegion lInt(lRect);
        lInt&=CRect_d(mTreeBox->mLastDragPoint,mTreeBox->mDragPoint);

        lGC->SetSource(img::CColor(0x7f7f6028));
        ///\todo lGC->FillRegion(lInt);
    }

    mTreeBox->EndPainting(lGC,CRegion(lRect));
}

int CTBTreeRenderer::HitTestNode(CTreeBox::SNode *pI,const CPoint_d &pP)
{
    CTBTreeRendererData *lD=(CTBTreeRendererData*)pI->mRendererData;

    CPoint_d lP=pP-lD->mPos+mTreeBox->GetScrollPos();

    if(lP.x<0||lP.y<0||lP.x>=mTotalEnd||lP.y>=lD->mHeight)
    {
        return LBHT_NONE;
    }

    if(CRect_d(lD->mIconPos,mIconSize).Contains(lP))
    {
        return LBHT_ICON|LBHT_SELECT|LBHT_REGION;
    }

    if(CRect_d(lD->mTextPos,lD->mTextLayout.GetSize()).Contains(lP))
    {
        return LBHT_TEXT|LBHT_SELECT|LBHT_REGION;
    }

    if(pI->CanBeExpanded())
    {
        if(lP.x>=lD->mExpandPos.x-4&&
            lP.x<=lD->mExpandPos.x+4&&
            lP.y>=lD->mExpandPos.y-4&&
            lP.y<=lD->mExpandPos.y+4)
        {
            return LBHT_EXPAND|LBHT_SELECT;
        }
    }

    return LBHT_REGION|LBHT_SELECT;
}

int CTBTreeRenderer::HitTestNode(CTreeBox::SNode *pI,const CRect_d &pR)
{
    CTBTreeRendererData *lD=(CTBTreeRendererData*)pI->mRendererData;

    CRect_d lR=pR-lD->mPos+mTreeBox->GetScrollPos();

    if(lR.r<0||lR.b<0||lR.l>=mTotalEnd||lR.t>=lD->mHeight)
        return LBHT_NONE;

    if(lR.l<0&&lR.t<0&&lR.r>=mTotalEnd&&lR.b>=lD->mHeight)
        return LBHT_TOTAL;

    if(lR.r>=lD->mIconPos.x&&lR.b>=lD->mIconPos.y&&
            lR.l<lD->mIconPos.x+mIconSize.x&&
            lR.t<lD->mIconPos.y+mIconSize.y)
        return LBHT_PARTIAL;

    return LBHT_NONE;
}

void CTBTreeRenderer::PreshapeAll()
{
    mMaxIconWidth=0;
    mMaxHeight=0;
    if(mTreeBox->GetStyle(CTreeBox::STYLE_HIERARCHYLINES))
        mIndent=14;
    else
        mIndent=11;
    if(!mTreeBox->GetNodeCount()) return;

    CTreeBox::SNode *lNode;
    for(lNode=mTreeBox->GetNodes()[0];lNode;lNode=lNode->GetNextVisible())
    {
        PreshapeNode(lNode);
    }

    if(mMaxHeight==0)
        mMaxHeight=mMinTextHeight+4;
}

void CTBTreeRenderer::ShapeAll()
{
    if(!mTreeBox->GetNodeCount()) return;

    CTreeBox::SNode *lNode;
    for(lNode=mTreeBox->GetNodes()[0];lNode;lNode=lNode->GetNextVisible())
    {
        ShapeNode(lNode);
    }
}

void CTBTreeRenderer::PosAll(int /*pFrom*/)
{
    CSize_d lSize=mTreeBox->Size();

    double lY=0;
    int lPosIndex=0;

    CTreeBox::SNode *lLast;
    CTreeBox::SNode *lNode;

    mNumRows=0;

    for(lNode=mTreeBox->GetNodeCount()?mTreeBox->GetNodes()[0]:nullptr;lNode;)
    {
        CTBTreeRendererData *lD=(CTBTreeRendererData*)lNode->mRendererData;

        mNumRows++;

        lD->mPos.x=0;
        lD->mPos.y=lY;
        lD->mPosIndex=lPosIndex;

        lY+=mMaxHeight;
        lPosIndex++;

        lLast=lNode;

        lNode=lNode->GetNextVisible();

        lLast->mNextVisible=lNode;
    }

    int lScrollRange=(int)(mMaxHeight*mNumRows-lSize.y);
    if(lScrollRange<=0)
    {
        mTreeBox->GetVScroll()->SetRange(0,0);
        mTreeBox->GetVScroll()->Hide();
    }
    else
    {
        mTreeBox->GetVScroll()->SetRange(0,lScrollRange);
        mTreeBox->GetVScroll()->SetStep((int)mMaxHeight,(int)lSize.y);
        mTreeBox->GetVScroll()->Show();
    }
    mTreeBox->GetHScroll()->SetRange(0,0);
    mTreeBox->GetHScroll()->Hide();

    mTreeBox->GetHScroll()->SetValue((int)mTreeBox->GetScrollPos().x);
    mTreeBox->GetVScroll()->SetValue((int)mTreeBox->GetScrollPos().y);
}

void CTBTreeRenderer::DrawAll(img::CGC *pGC)
{
    DrawAll(pGC,CRect_d({0,0},mTreeBox->Size()));
}

void CTBTreeRenderer::DrawAll(img::CGC *pGC,const CRect_d &pRect)
{
    CRect_d lRect({0,0},mTreeBox->Size());

    if(!mTreeBox->GetNodeCount())
    {
        pGC->SetSource(gConfig.mColors.Get(L"normal_text"));

        CPoint_d lPoint(lRect.r/2-mFont->GetWidth(mTreeBox->mEmptyText)/2,
                      lRect.b/2-mFont->GetHeight()/2);

        pGC->DrawText(lPoint,mTreeBox->mEmptyText);

        return;
    }

    CTBTreeRendererData *lD=nullptr;

    for(CTreeBox::SNode *lNode=mTreeBox->GetNodes()[0];lNode;lNode=lNode->mNextVisible)
    {
        lD=(CTBTreeRendererData*)lNode->mRendererData;

        double lY=lD->mPos.y-mTreeBox->GetScrollPos().y;

        if(lY>pRect.b+1||
           lY+lD->mHeight<pRect.t) continue;
        DrawNode(pGC,lNode);
    }

    double lY=-mTreeBox->GetScrollPos().y;
    //int i=0;

    if(lD)
    {
        lY+=lD->mPos.y+mMaxHeight;
        //i=lD->mPosIndex+1;
    }

    /*
    for(;lY+mMaxHeight<pRect.b;lY+=mMaxHeight)
    {
        if(lY+mMaxHeight<pRect.t) continue;
        if(!((i++)%2)) continue;
        pGC->SetSource(RGB(237,243,254));
        pGC->Path().Rect(CPoint_d(0,lY),CSize_d(mTotalEnd,mMaxHeight));
        pGC->FillPath();
    }
    */
}

///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////
/////////////////////// Event handlers ////////////////////////
///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////

void CTBTreeRenderer::OnSize(const CGeometryEvent &pE)
{
    CalcAll();
}

void CTBTreeRenderer::OnExpose(const CExposeEvent &pE)
{
    img::CGC *lGC=pE.mGC;

    lGC->SetSource(gConfig.mColors.Get(L"normal_text"));
    lGC->SetFont(mFont);
    DrawAll(lGC,pE.mRect);
}

void CTBTreeRenderer::OnBigChange()
{
    CalcAll();
    mTreeBox->Refresh();
}

void CTBTreeRenderer::OnNodeSelected(CTreeBox::SNode *pNode)
{
    RedrawNode(pNode,true);
}

void CTBTreeRenderer::OnNodeAdded(CTreeBox::SNode *pNode)
{
    InitNode(pNode);
    PreshapeNode(pNode);
    ShapeNode(pNode);

    if(mTreeBox->GetAutoUpdate())
    {
        if(pNode->mParent&&!pNode->mParent->mExpanded)
        {
            PosAll();
            if(pNode->mParent->mChildren.size())
            {
                RedrawNode(pNode->mParent,false);
            }
        }
        else
        {
            OnBigChange();
        }
    }
}

void CTBTreeRenderer::OnNodeChanged(CTreeBox::SNode *pNode)
{
    ShapeNode(pNode);
    RedrawNode(pNode,true);
}

void CTBTreeRenderer::OnNodeRemoved(CTreeBox::SNode *pI)
{
    DeinitNode(pI);
    PosAll();
}

void CTBTreeRenderer::OnCursorChanged(CTreeBox::SNode *pNode)
{
    ShapeNode(pNode);
    RedrawNode(pNode,true);
}

void CTBTreeRenderer::OnFieldsChanged()
{
    CalcAll();
    mTreeBox->Refresh();
    FillRuler();
}

void CTBTreeRenderer::OnXScrollChanged(int pValue)
{
    if(mRuler)
        mRuler->SetScroll(pValue);
}

void CTBTreeRenderer::OnRulerMoved(TID pID,double pSize)
{
    double lChange;
    if(pID==-1)
    {
        lChange=pSize-mFirstField->mW;
        mFirstField->mW=pSize;
    }
    else
    {
        lChange=pSize-mTreeBox->mTWF[pID]->mW;
        mTreeBox->mTWF[pID]->mW=pSize;
    }

    for(int i=pID+1;i<mTreeBox->mTWF.size();i++)
    {
        mTreeBox->mTWF[i]->mW+=lChange;
    }

    CalcAll();
    mTreeBox->Refresh();
}

CRect_d CTBTreeRenderer::GetNodeRect(CTreeBox::SNode *pI)
{
    CTBTreeRendererData *lD=(CTBTreeRendererData*)pI->mRendererData;

    return CRect_d(lD->mPos-mTreeBox->GetScrollPos(),CSize_d(mTotalEnd,lD->mHeight));
}

PEditBox CTBTreeRenderer::Edit(CTreeBox::SNode *pI)
{
    CTBTreeRendererData *lD=(CTBTreeRendererData*)pI->mRendererData;

    PEditBox lEditBox=new CEditBox;
    lEditBox->MoveResize(lD->mPos+lD->mTextPos-mTreeBox->GetScrollPos()-
        CPoint_d(3,3),CSize_d(mTextEnd-lD->mPos.x,lD->mTextLayout.GetSize().y+5));
    lEditBox->SetCaption(pI->mText);
    lEditBox->SelectAll();
    mTreeBox->Place(lEditBox);
    lEditBox->SetFocus();

    return lEditBox;
}

void CTBTreeRenderer::FillRuler()
{
    if(!mRuler) return;

    mRuler->RemoveFields();

    mRuler->AddField(-1,mFirstField->mField->mTitle,nullptr,mFirstField->mW);

    int lCount=0;
    
    for(int i=0;i<mTreeBox->mTWF.size();i++)
    {
        if(mTreeBox->mTWF[i]->mField->mStyle&CPropertyField::PLF_HIDDEN) continue;
        lCount++;

        mRuler->AddField(i,mTreeBox->mTWF[i]->mField->mTitle,nullptr,
                            mTreeBox->mTWF[i]->mW);
    }
    if(lCount>1)
        mRuler->Show();
    else
        mRuler->Hide();
}

/*namespace gui*/ } /*namespace buola*/ }
